// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import global from '../config/global';

class Logger {
  constructor(level = Logger.level.warn, tags = [], size = 10000, isSilent = false) {
    this.level = level;
    this.tags = tags;
    this.size = size;
    this.isSilent = isSilent;
    this.timeline = [];
  }

  echo(level, tags, ...message) {
    let logLevel = Logger.level[level];
    // 生产环境非最高级别错误日志直接退出
    if (!global.__IS_DEBUG__ && logLevel < Logger.level.error) {
      return;
    }

    // 允许不传递 tags，此时 tag 用各个 level 对应的默认值
    if (typeof tags === 'string') {
      message.unshift(tags);
      tags = [Logger.tag[level]];
    }

    if (logLevel >= this.level && this.match(tags, this.tags)) {
      const now = performance && performance.now();
      this.cache(now, tags, ...message);
      if (!this.isSilent) {
        this.triggerLogCallback(logLevel, now, tags, message);
      }
    }
  }

  match(tags1 = [], tags2 = []) {
    for (let i = 0, len = tags1.length; i < len; i++) {
      if (tags2.includes(tags1[i])) {
        return true;
      }
    }

    return false;
  }

  reset() {
    Logger.cache = [];
    Logger.callbacks = [];
  }

  cache(timestamp, tags, ...message) {
    if (Logger.cache.length >= this.size) {
      Logger.cache.shift();
    }

    Logger.cache.push({
      timestamp,
      tags: tags,
      message: message.join('| ')
    });
  }

  ana() {
    let logs = Logger.cache;
    let tmp = {};
    logs.forEach((item) => {
      let tags = item.tags || [];
      let traceId = tags.find && tags.find((tag) => tag.startsWith('trace_'));
      let name = tags.filter && tags.filter((tag) => !tag.startsWith('trace_')).join(', ');
      if (traceId) {
        !tmp[traceId] && (tmp[traceId] = { id: traceId });
        tmp[traceId][name] = item.timestamp;
      }
    });
    let allTotal = 0;
    let allDecode = 0;
    let allRender = 0;
    let allOther = 0;
    let result = Object.keys(tmp).map((trace) => {
      let total = tmp[trace]['decode, end'] - tmp[trace]['package, begin'];
      let decode = tmp[trace]['decode, end'] - tmp[trace]['decode, begin'];
      let render = tmp[trace]['render, end'] - tmp[trace]['render, begin'];
      let other = total - decode;
      allTotal += total;
      allDecode += decode;
      allRender += render;
      allOther += other;
      tmp[trace]['other/render/decode/total'] = `${other}/${render}/${decode}/${total}`;
      return tmp[trace];
    });
    const len = result.length;
    result.unshift({
      id: '平均',
      'other/render/decode/total': `${allOther / len}/${allRender / len}/${allDecode / len}/${allTotal / len}`
    });
    result.unshift({
      id: '总计',
      'other/render/decode/total': `${allOther}/${allRender}/${allDecode}/${allTotal}`
    });
    this.debug([Logger.tag.debug], result);
  }

  analysis() {
    let logs = Logger.cache;
    let result = [];
    let start;
    let recv = [];
    let video = [];
    logs.forEach((item) => {
      if (item.tags.includes('delay')) {
        if (start) {
          result.push({
            start: start,
            end: item,
            recv: recv,
            video: video,
            period: item.timestamp - start.timestamp + 'ms',
            delayChange: (Number(item.message) - Number(start.message)) * 1000 + 'ms',
            recvLen: recv.length + '帧',
            videoLen: video.length + '帧'
          });
          recv = [];
          video = [];
        }

        start = item;
      } else if (item.tags.includes('video')) {
        video.push(item);
      } else if (item.tags.includes('recv')) {
        recv.push(item);
      }
    });
    this.debug([Logger.tag.debug], result);
    return result;
  }

  videoAna() {
    let logs = Logger.cache;
    let tmp = {};
    logs.forEach((item) => {
      let tags = item.tags || [];
      let traceId = tags.find && tags.find((tag) => tag.startsWith('trace_'));
      let name = tags.filter && tags.filter((tag) => !tag.startsWith('trace_')).join(', ');
      if (traceId) {
        !tmp[traceId] && (tmp[traceId] = { id: traceId });
        // 'event, appending, buffer' 用于查看video缓存信息
        // 'recv, diffT' 用于计算接收视频数据间隔
        if (name === 'event, appending, buffer' || name === 'recv, diffT') {
          tmp[traceId][name] = item.message;
        } else {
          tmp[traceId][name] = item.timestamp;
        }
      }
    });
    let allTotal = 0;
    let allFormat = 0;
    let allAppending1 = 0;
    let allAppending2 = 0;
    let allToBuffer = 0;

    let maxTotal = 0;
    let maxFormat = 0;
    let maxAppending1 = 0;
    let maxAppending2 = 0;
    let maxToBuffer = 0;
    let result = Object.keys(tmp).map((trace) => {
      let total = Number(
        (tmp[trace]['event, appendBuffer, 1'] - tmp[trace]['analysis, end']).toFixed(3)
      );
      // let analysis = (tmp[trace]['analysis, end'] - tmp[trace]['event, recv']).toFixed(3));
      let format = Number((tmp[trace]['format, end'] - tmp[trace]['analysis, end']).toFixed(3));
      let appending1 = Number(
        (tmp[trace]['event, appendBuffer, 0'] - tmp[trace]['format, end']).toFixed(3)
      );
      let appending2 = Number(
        (tmp[trace]['event, appendBuffer, 1'] - tmp[trace]['event, appendBuffer, 0']).toFixed(3)
      );
      let formatToBuffer = Number(
        (tmp[trace]['event, appendBuffer, 1'] - tmp[trace]['format, end']).toFixed(3)
      );

      maxTotal = Math.max(maxTotal, total);
      maxFormat = Math.max(maxFormat, format);
      maxAppending1 = Math.max(maxAppending1, appending1);
      maxAppending2 = Math.max(maxAppending2, appending2);
      maxToBuffer = Math.max(maxToBuffer, formatToBuffer);

      allTotal += total;
      allFormat += format;
      allAppending1 += appending1;
      allAppending2 += appending2;
      allToBuffer += formatToBuffer;
      tmp[trace]['format/appending1/appending2/formatToBuffer/total'] =
        `${format}/${appending1}/${appending2}/${formatToBuffer}/${total}`;
      return tmp[trace];
    });
    const resultLen = result.length;
    result.unshift({
      id: '最大值',
      'format/appending1/appending2/formatToBuffer/total': `${maxFormat}/${maxAppending1}/${maxAppending2}/${maxToBuffer}/${maxTotal}`
    });
    result.unshift({
      id: '平均',
      'format/appending1/appending2/formatToBuffer/total': `${(allFormat / resultLen).toFixed(3)}/${(allAppending1 / resultLen).toFixed(3)}/${(allAppending2 / resultLen).toFixed(3)}/${(allToBuffer / resultLen).toFixed(3)}/${(allTotal / resultLen).toFixed(3)}`
    });
    result.unshift({
      id: '总计',
      'format/appending1/appending2/formatToBuffer/total': `${allFormat.toFixed(3)}/${allAppending1.toFixed(3)}/${allAppending2.toFixed(3)}/${allToBuffer.toFixed(3)}/${allTotal.toFixed(3)}`
    });

    this.debug([Logger.tag.debug], result);
    this.debug(
      [Logger.tag.debug],
      '接收视频帧间隔时间大于70',
      result.filter((item) => item['recv, diffT'] && Number(item['recv, diffT']) > 70)
    );
    // 计算帧率
    const videoResult = result.filter((item) => Boolean(item['analysis, end']));
    const videoResultLen = videoResult.length;
    if (videoResultLen > 0) {
      const firstTime = videoResult[0] && Number(videoResult[0].id.slice(6));
      const lastTime =
        videoResult[videoResultLen - 1] && Number(videoResult[videoResultLen - 1].id.slice(6));
      this.debug(
        [Logger.tag.debug],
        'fps: ' + ((videoResultLen - 1) * 1000) / (lastTime - firstTime)
      );
    }
  }

  table(tags) {
    let list = Logger.cache;
    if (tags && tags.length) {
      list = Logger.cache.filter((item) => this.match(tags, item.tags));
    }

    this.debug([Logger.tag.debug], list);
  }

  toCSV() {
    let list = Logger.cache.map((item) => `${String(item.timestamp)},${item.tags},${item.message}`);
    list.unshift('timestamp,tags,message');
    return [list.join('\n')];
  }

  export() {
    let data = new Blob(this.toCSV(), { type: 'application/octet-stream' });
    let anchor = document.createElement('a');
    anchor.href = window.URL.createObjectURL(data);
    anchor.download = `log-${Date.now()}.csv`;
    document.body.appendChild(anchor);
    anchor.click();
    document.body.removeChild(anchor);
    window.URL.revokeObjectURL(data);
  }

  debug(tags, ...args) {
    this.echo('debug', tags, ...args);
  }

  info(tags, ...args) {
    this.echo('info', tags, ...args);
  }

  warn(tags, ...args) {
    this.echo('warn', tags, ...args);
  }

  error(tags, ...args) {
    this.echo('error', tags, ...args);
  }

  triggerLogCallback(echoLevel, time, tags, message) {
    Logger.callbacks.forEach((item) => {
      // 触发级别等于或低于 echoLevel 的所有事件，高级别消息通知可以触发低级别事件，低级别消息通知不能触发高级别事件
      if (echoLevel >= item.level && this.match(item.tags, tags)) {
        item.func({ time, tags, message });
      }
    });
  }

  onLogReceived(func) {
    if (typeof func === 'function') {
      Logger.callbacks.push({
        level: this.level,
        tags: this.tags,
        func
      });
    }
  }
}
Logger.cache = [];
Logger.callbacks = [];
Logger.level = {
  debug: 1,
  info: 2,
  warn: 3,
  error: 4
};
Logger.tag = {
  debug: 'DEBUG',
  info: 'INFO',
  warn: 'WARN',
  error: 'ERROR'
};

// 生产环境限制日志级别为最高级，用户只能获取错误日志
let logLevel = Logger.level.error;
let logTags = [Logger.tag.error];
if (global.__IS_DEBUG__) {
  logLevel = Logger.level.debug;
  logTags.push(Logger.tag.debug);
}

let logger = new Logger(logLevel, logTags);
export default logger;
